package terminal.communication;

import java.net.BindException;
import java.util.UUID;

import javax.inject.Inject;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import terminal.gui.ConnectionStatusWindow;
import terminal.gui.MainWindow;
import core.communication.message.Direction;
import core.communication.message.ElectionCardMessage;
import core.communication.message.Message;
import core.communication.message.MessagePriorityQueue;
import core.communication.message.TerminalRegistrationMessage;
import core.communication.message.VoteMessage;
import core.exceptions.CommunicationInitiationException;
import core.exceptions.PortInUseException;
import core.utils.LocatorUriChecker;

@Service
public class CommunicationServiceImpl implements CommunicationService {

	private static final Logger L = LoggerFactory.getLogger(CommunicationServiceImpl.class);

	private Server server;
	private MessageSender messageSender;
	private MessageReceiver messageReceiver;
	private MessagePriorityQueue messagesToSend = new MessagePriorityQueue();
	private MessagePriorityQueue messagesReceived = new MessagePriorityQueue();
	private Client rkwClient;
	private ThreadPoolTaskExecutor taskExecutor;
	private UUID communicationToken;
	private ConnectionStatusWindow connectionStatusWindow = null;
	private MainWindow mainWindow = null;

	@Inject
	public CommunicationServiceImpl(Server server, MessageSender messageSender, MessageReceiver messageReceiver, ThreadPoolTaskExecutor taskExecutor) {
		L.debug("Tworzę CommunicationService");

		Assert.notNull(server);
		Assert.notNull(messageSender);
		Assert.notNull(messageReceiver);
		Assert.notNull(taskExecutor);

		this.server = server;
		this.messageSender = messageSender;
		this.messageReceiver = messageReceiver;
		this.taskExecutor = taskExecutor;

		L.debug("Utworzono CommunicationService");
	}

	// TODO: Co jak w trakcie wysyłania (resend), ktoś kliknie anuluj?
	@Async
	@Override
	public void setUp(String serverIp, String serverPort, String clientIp, String clientPort, UUID communicationToken, ConnectionStatusWindow connectionStatusWindow)
			throws CommunicationInitiationException, PortInUseException {
		L.debug("setUp"); 

		this.connectionStatusWindow = connectionStatusWindow;

		if (!LocatorUriChecker.checkIfLocatorUriIsOpen(serverIp, serverPort)) {
			L.error("Wrong server locatorURI.");
			connectionStatusWindow.setConnectionNotEstablished();
			throw new CommunicationInitiationException("Wrong server locatorURI.");
		}

		try {
			server.setUp(LocatorUriChecker.getLocatorURI(serverIp, serverPort));
		} catch (BindException e) {
			L.error("The port is already in use.");
			connectionStatusWindow.setConnectionNotEstablished();
			throw new PortInUseException("The port is already in use.");
		}

		rkwClient = new Client(LocatorUriChecker.getLocatorURI(clientIp, clientPort));

		this.communicationToken = communicationToken;

		taskExecutor.execute(messageSender);
		taskExecutor.execute(messageReceiver);

		registerToRKW();
	}

	private void registerToRKW() {
		TerminalRegistrationMessage msg = new TerminalRegistrationMessage(server.getLocatorURI(), communicationToken, Direction.UP);
		send(msg);
	}

	@Override
	public void shutDown() {
		L.debug("shutDown");

		server.shutdownServer();
		messageSender.stop();
		messageReceiver.stop();
	}

	@Override
	public Message getMessageToSend() {
		Message message = null;
		synchronized (messagesToSend) {
			message = messagesToSend.get();
		}

		return message;
	}

	@Override
	public Client getRKWClient() {
		return rkwClient;
	}

	@Override
	public void removeMessageToSend(Message message) {
		L.debug("Usuwam wiadomośc {}", message.toString());
		synchronized (messagesToSend) {
			messagesToSend.remove(message);
		}
	}

	@Override
	public void resendMessage(Message message) {
		synchronized (messagesToSend) {
			messagesToSend.remove(message);
			messagesToSend.add(message);
			messageSender.wakeUp();
		}
	}

	@Override
	public Message getReceivedMessage() {
		Message message = null;
		synchronized (messagesReceived) {
			message = messagesReceived.get();
		}
		return message;
	}

	@Override
	public void removeReceivedMessage(Message message) {
		messagesReceived.remove(message);
	}

	@Override
	public String getServerURI() {
		return server.getLocatorURI();
	}

	@Override
	public void addReceivedMessage(Message message) {
		L.info("Dodaje otrzymaną wiadomośc {}", message.toString());
		synchronized (messagesReceived) {
			messagesReceived.add(message);
			messageReceiver.wakeUp();
		}
	}

	@Override
	public void send(Message message) {
		L.debug("Wysyłam wiadomość {}", message);

		message.setDestinationURI(rkwClient.getLocatorURI());

		synchronized (messagesToSend) {
			messagesToSend.add(message);
			messageSender.wakeUp();
		}
	}

	@Override
	public void handleTerminalRegistrationMessage(TerminalRegistrationMessage message) {
		if (connectionStatusWindow != null) {
			if (message.getRegistrationConfirmation())
				connectionStatusWindow.setConnectionEstablished();
			else
				connectionStatusWindow.setConnectionNotEstablished();
		}
	}

	@Async
	@Override
	public void askForElectionCard(MainWindow mainWindow) {
		this.mainWindow = mainWindow;
		ElectionCardMessage msg = new ElectionCardMessage(null, communicationToken, Direction.UP, server.getLocatorURI());
		send(msg);
	}

	@Override
	public void handleElectionCardMessage(ElectionCardMessage message) {
		mainWindow.createVotingCmp(message.getCard());
	}

	@Override
	public void sendVote(String vote, UUID token, MainWindow mainWindow) {
		messageSender.setMainWindow(mainWindow);
		VoteMessage msg = new VoteMessage(vote, communicationToken, token);
		send(msg);
	}
}
